/**
 * 
 * AxLib
 * 
 * Author: WaveF
 * QQ: 298010937
 * 
 * UPDATE:
 * 
 * v3.2.5
 * - 增加 setPanelStateByIndex 方法
 * - 修复了 getCurrentPanelState 的一个导致无法执行的bug
 * 
 * v3.2.4
 * - 增加 getCurrentPanelState 方法
 * - 修正 getPanelStates 返回值有误的问题
 * 
 * v3.2.3
 * - 增加从中继器侦听获取参数
 * 
 * v3.2.1
 * - 增加 getRepeaterDataDiff 方法获取中继器初始化与交互追加后的差异数据
 * 
 * v3.2.0
 * - 内置 yepnope.min.js (1.5.x)
 * - 增加 use 方法用于统一加载其他模块，移除原有的 useToast
 * - 移除 testExp 里的 eval 方法
 * - 仅保留最后两次更新描述
 * 
 **/

(function () {

  let _plugins;

  main();

  function main() {
    if (!$axure || !$) {
      trace('请先加载 $axure 和 jquery！');
      return;
    }

    Utils();
    InitAxlib();

    _plugins = {
      toast: {
        vars: '$.toast',
        urls: [
          'https://ax.minicg.com/plugins/toast/jquery.toast.min.css',
          'https://ax.minicg.com/plugins/toast/jquery.toast.min.js'
        ]
      },
      swal2: {
        vars: 'Swal',
        urls: [
          'https://ax.minicg.com/plugins/swal2/bootstrap-4.min.css',
          'https://ax.minicg.com/plugins/swal2/sweetalert2.min.js'
        ]
      },
    };

    useYepNope(function(){
      // 附加一些常用方法
      window.AXLIB = window.axlib = {
        name: 'axlib', version: '3.2.5',

        use, // 加载其他模块
        layout, // 预设的界面元素显隐及缩放
        trace, // 显示一段预设样式的文本
        jsonbin, // 调用jsonbin v2的方法在线读写数据
        hosting, // 参数定义同步到哪里，默认是axhub
        loading, // 插入一个加载动画
        find, // 便捷提取指定元件名的对象，示例：let els = axlib.find('sym1', 'sym2'...); let { sym1, sym2 } = els;
        saveTextFile, // 传入字符串与文件名保存文件
        testExp, // 等待测试表达式的方法
        loadJsModule, // 以 es module 方式加载 js
        
        formatRepeaterData: $axure.ex.formatRepeaterData,
        setGlobalVar: $axure.setGlobalVariable,
        getGlobalVar: $axure.getGlobalVariable,
        getOverlapData: $axure.getOverlapData,
        getDiffData: $axure.getDiffData,
  
      };
    });
  }

  function InitAxlib() {
    $axure.ex = { fn: {} };

    // _showMsg = new Function( 'return ' + top.$axure.messageCenter.dispatchMessageRecursively.toString() );

    // 改写并拓展Axure的私有对象
    $axure.internal(function ($ax) {

      /**
       * 增强中继器相关方法
       * 注：仅支持单个操作，要确保每个元素都有不同ID，就可以用循环逐个修改
       */

      // 刷新数据（要定义在最前面，因为后面会引用到）
      $axure.ex.refreshRepeater = $ax.repeater.refreshRepeater;
      $ax.public.fn.refreshRepeater = function () {
        let elementId = this.getElementIds()[0];
        $axure.ex.refreshRepeater(elementId);
        return this;
      };
      $ax.repeater.refreshRepeater = function(elementId, etc) {
        $axure.ex.refreshRepeater(...arguments);
        $(`#${elementId}`).trigger('REPEATER_REFRESH');
      };

      // 好像是获取行号，从1开始索引，因为后面要用到所以保留
      $axure.ex.getRepeaterRows = $ax.repeater.getAllItemIds;
      $ax.public.fn.getRepeaterRows = function () {
        return $axure.ex.getRepeaterRows(this.getElementIds()[0]);
      };

      // 通过ID获取实例
      $axure.ex.getRepeater = function (elementId) {
        let repeater;
        $axure(function (obj) {
          return obj.type == 'repeater';
        }).each(function (obj, id) {
          if (id == elementId) {
            repeater = obj;
          }
        });
        return repeater;
      };

      // 格式化数据
      $axure.ex.formatRepeaterData = function(data, options) {
        // 根据 options 参数对数据进行格式化
        options = options || { format:'auto', key:undefined, first:false, trim:true };
        let newData;
        let format = options['format'].toLowerCase();
        if (format === 'default') {
          newData = data;
        } else if (format === 'auto') {
          let arr = [];
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            let obj = {};
            for (let k in item) {
              obj[k] = item[k].text;
            }
            arr.push(obj);
          }
          newData = arr;
        } else if (format === 'row') {
          let result = {};
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            let obj = {};
            let key = Object.values(item)[0].text;
            let val = [];
            for (let k in item) {
              let sub = item[k];
              if (sub.text != key) {
                val.push(sub.text);
              }
            }
            if (options.first) { val = val[0]; }
            obj[key] = val;
            $.extend(result, obj);
          }
          newData = result;
        } else if (format === 'column' || format === 'col') {
          let obj = {};
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            for (k in item) {
              if (!obj[k]) {
                obj[k] = [item[k].text];
              } else {
                obj[k].push(item[k].text);
              }
            }
          }
          newData = obj;
        } else if (format === 'key') {
          if (!options.key) {
            console.log('未提供Key名，将不会进行格式化...');
            newData = data;
          } else {
            let result = {};
            let pKey = options.key;
            for (let i = 0; i < data.length; i++) {
              let item = data[i];
              let key = item[pKey].text;
              let val = [];
              let obj = {};
              for (let k in item) {
                if (k != pKey) {
                  val.push(item[k].text);
                }
              }
              if (options.first) { val = val[0]; }
              obj[key] = val;
              $.extend(result, obj);
            }
            newData = result;
          }
        } else if (format === 'tree') {
          let arr = [];
          for (let i = 0; i < data.length; i++) {
            let item = data[i];
            let obj = {};
            for (let k in item) { obj[k] = item[k].text; }
            arr.push(obj);
          }
          newData = tree(arr);
          options.trim && trimTreeData(newData);
        } else {
          trace(`未注册的格式化参数: ${format}，请使用此定义 { format: 'auto' || 'row' || 'column' || 'tree' || 'key' } ，其中 key 模式需额外提供索引键列名称.`);
          newData = data;
        }
        return newData;
      }
      $axure.formatRepeaterData = $axure.ex.formatRepeaterData;

      // 获取数据
      $axure.ex.getRepeaterData = function (elementId) {
        let ids = $ax.repeater.getAllItemIds(elementId);
        let columns = $axure.ex.getRepeater(elementId).dataProps;
        rows = [];
        for (let i = 0, il = ids.length; i < il; i++) {
          let row = {};
          for (let j = 0, jl = columns.length; j < jl; j++) {
            let name = columns[j].toLowerCase();
            let id = ids[i];
            if ((typeof (id) == 'string') && (id.indexOf('-') != -1))
              id = $ax.repeater.getItemIdFromElementId(id);
            let value = $ax.repeater.getData({}, elementId, ids[i], name, 'data');
            if (typeof (value) == 'object') {
              value = $ax.deepCopy(value);
              if (value.type === undefined) {
                value.type = 'text';
              }
              row[name] = value;
            } else {
              row[name] = {
                type: 'text',
                text: value
              };
            }
          }
          rows.push(row);
        }
        return rows;
      };
      $ax.public.fn.getRepeaterData = function (options) {
        let elementId = this.getElementIds()[0];
        let data = $axure.ex.getRepeaterData(elementId);
        data = $axure.ex.formatRepeaterData(data, options);
        return data;
      };

      // 获取初始化数据
      $axure.ex.getRepeaterDataOnce = function (elementId) {
        try {
          var result;
          // $axure(`#${id}`).refreshRepeater();
          return window.$axure((function (elementId) {
              return "repeater" === elementId.type
          })).each((function (obj, id) {
            elementId === id && (result = obj)
          })), result.data;
        } catch (err) {
            return console.error("Error：Can not get repeater data by id " + err);
        }
      };
      $ax.public.fn.getRepeaterDataOnce = function (options) {
        let elementId = this.getElementIds()[0];
        let data = $axure.ex.getRepeaterDataOnce(elementId);
        data = $axure.ex.formatRepeaterData(data, options);
        return data;
      };
      $axure.ex.getRepeaterInitedData = $axure.ex.getRepeaterDataOnce;
      $ax.public.fn.getRepeaterInitedData = $ax.public.fn.getRepeaterDataOnce;

      // 获取初始化与交互追加后的差异数据
      $axure.ex.getRepeaterDataDiff = function (elementId) {
        let data = $axure.ex.getRepeaterData(elementId);
        let dataOnce = $axure.ex.getRepeaterDataOnce(elementId);
        data.splice(0, dataOnce.length);
        return data;
      };
      $ax.public.fn.getRepeaterDataDiff = function (options) {
        let elementId = this.getElementIds()[0];
        let diff = $axure.ex.getRepeaterDataDiff(elementId);
        return $axure.ex.formatRepeaterData(diff, options);
      };

      // 获取初始化与交互追加后的差异数据
      $axure.ex.getRepeaterDataSame = function (elementId) {
        let data = $axure.ex.getRepeaterData(elementId);
        let dataOnce = $axure.ex.getRepeaterDataOnce(elementId);
        data.splice(dataOnce.length, data.length);
        return data;
      };
      $ax.public.fn.getRepeaterDataSame = function (options) {
        let elementId = this.getElementIds()[0];
        let diff = $axure.ex.getRepeaterDataSame(elementId);
        return $axure.ex.formatRepeaterData(diff, options);
      };

      // 追加数据
      $axure.ex.repeaterAddItem = $ax.repeater.addItem;
      $axure.ex.addRepeaterData = function (elementId, rows) {
        let event = { targetElement: undefined, srcElement: undefined };
        let repeater = $axure.ex.getRepeater(elementId);
        let columns = repeater.dataProps;
        let itemDefaultValue = { type: 'text', text: '' };

        for (let i = 0, ilen = rows.length; i < ilen; i++) {
          let source = rows[i];
          let target = {};
          for (let j = 0, jlen = columns.length; j < jlen; j++) {
            let column = columns[j];
            let item = source[column];

            if (item === undefined) {
               item = itemDefaultValue;
            } else if (!item.hasOwnProperty('type') && !item.hasOwnProperty('text')) {
              item = { type: 'text', text: item };
            } else {
              item = $ax.deepCopy(item);
            }
            target[column] = item;
          }
          $ax.repeater.addItem(elementId, target, event);
        }
      };
      $ax.public.fn.addRepeaterData = function (rows) {
        let elementId = this.getElementIds()[0];
        $axure.ex.addRepeaterData(elementId, rows);
        $axure.ex.refreshRepeater(elementId);
        return this;
      };
      // 这里会触发原生交互的添加数据
      $ax.repeater.addItem = function() {
        let elementId = arguments[0];
        $axure.ex.repeaterAddItem(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_ADDED', {...arguments});
      };


      // 移除N行数据
      $axure.ex.deleteItems = $ax.repeater.deleteItems;
      $ax.public.fn.deleteRepeaterData = function (rowsCount) {
        rowsCount = rowsCount || 1;
        let data = this.getRepeaterData();
        let newData = data.slice(0, data.length - rowsCount);
        let elementId = this.getElementIds()[0];
        $axure.ex.clearRepeaterData(elementId);
        $axure.ex.addRepeaterData(elementId, newData);
        $axure.ex.refreshRepeater(elementId);
        return this;
      };
      // 这里会触发原生交互的删除数据
      $ax.repeater.deleteItems = function() {
        let elementId = arguments[0];
        $axure.ex.deleteItems(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_DELETED', {...arguments});
      }


      // PUSH数据（意义不明）
      $axure.ex.pushItems = $ax.repeater.pushItems;
      $ax.repeater.pushItems = function() {
        let elementId = arguments[0];
        $axure.ex.pushItems(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_PUSH', {...arguments});
      }
      
      
      // 更新数据
      $axure.ex.updateEditItems = $ax.repeater.updateEditItems;
      $ax.repeater.updateEditItems = function() {
        let elementId = arguments[0];
        $axure.ex.updateEditItems(...arguments);
        $(`#${elementId}`).trigger('REPEATER_DATA_UPDATED', {...arguments});
      }


      // 获取重叠部分数据
      $ax.public.fn.getOverlapData = function (extraRawData, format) {
        let elementId = this.getElementIds()[0];
        let rep = $axure(`#${elementId}`);
        let latestRawData = rep.getRepeaterData({format:'default'});
        let overlapData = getOverlapData(extraRawData, latestRawData);
        return $axure.ex.formatRepeaterData(overlapData, format);;
      };

      

      // 设置数据
      $ax.public.fn.setRepeaterData = function (rows) {
        let elementId = this.getElementIds()[0];
        $axure.ex.clearRepeaterData(elementId);
        $axure.ex.addRepeaterData(elementId, rows);
        $axure.ex.refreshRepeater(elementId);
        $(`#${elementId}`).trigger('REPEATER_DATA_SET', {...arguments});
        return this;
      };
      
      // 清空数据
      $axure.ex.clearRepeaterData = function (elementId) {
        let ids = $axure.ex.getRepeaterRows(elementId);
        $ax.repeater.addEditItems(elementId, ids);
        $ax.repeater.deleteItems(elementId, {}, 'marked', undefined);
      };
      $ax.public.fn.clearRepeaterData = function () {
        let elementId = this.getElementIds()[0];
        $axure.ex.clearRepeaterData(elementId);
        $axure.ex.refreshRepeater(elementId);
        return this;
      };


      /**
       * 增强动态面板相关方法
       * 注：仅支持单个操作，要确保每个元素都有不同ID，就可以用循环逐个修改
       */

      // 增强动态面板相关方法
      $axure.ex.getPanelStates = function (elementId) {
        let result = [];
        for (let index = 1;; index++) {
          let element = $('#' + elementId + '_state' + (index - 1));
          if (!element.length) break;
          let name = element.attr('data-label');
          result[index] = name;
          result[name] = index;
        }
        return result;
      };
      $ax.public.fn.getPanelStates = function () {
        let states = $axure.ex.getPanelStates(this.getElementIds()[0]);
        return states;
      };


      $axure.ex.getCurrentPanelState = function (elementId) {
        let current;
        let states = $(`#${elementId}`).children(`.panel_state`);
        $.each(states, (idx, item) => {
          if ($(item).css('visibility')!='hidden' && $(item).css('display')!='none') {
            current = item;
          }
        });
        return current;
      };
      $ax.public.fn.getCurrentPanelState = function () {
        let current = $axure.ex.getCurrentPanelState(this.getElementIds()[0]);
        return current;
      };


      $axure.ex.setPanelStateByName = function (elementId, stateName, options, showWhenSet) {
        let states = $axure.ex.getPanelStates(elementId);
        for (let k = 0; k < states.length; k++) {
          if (states[k] == stateName) {
            let stateNum = k;
            $axure('#' + elementId).SetPanelState(stateNum, options || {}, showWhenSet || false);
          }
        }
      };
      $ax.public.fn.setPanelStateByName = function (stateName, options, showWhenSet) {
        let elementId = this.getElementIds()[0];
        $axure.ex.setPanelStateByName(elementId, stateName, options, showWhenSet);
        return this;
      };
      $ax.public.fn.setPanelStateByIndex = function (index, options, showWhenSet) {
        let elementId = this.getElementIds()[0];
        $axure('#' + elementId).SetPanelState(index, options || {}, showWhenSet || false);
        return this;
      };


      /**
       * 简化获取ID与节点的方法名
       */
      $ax.public.fn.getId = function () {
        return this.getElementIds()[0];
      };
      $ax.public.fn.getEl = function () {
        return this.getElements()[0];
      };
      $axure.find = find;


      /**
       * 修正axure无法获取全局变量的bug
       */
      $ax.public.getGlobalVariable = $ax.getGlobalVariable = function (name) {
        return $ax.globalVariableProvider.getVariableValue(name);
      };


      /**
       * 添加一些常用样式控制
       */
      $ax.public.fn.buttonMode = function (flag) {
        this.$().css('cursor', flag ? 'pointer' : 'default');
      };
      $ax.public.fn.ghostMode = function (flag) {
        this.$().css('pointer-events', flag ? 'none' : 'auto');
      };

      
      /**
       * 跳转页面或链接
       */

      // 设置页面跳转前的Hook
      // 注：页面载入时没有hook，如果要做载入前的转场动画需要自行处理，因为axlib开始执行的时间始终慢于页面渲染时
      $axure.ex.redirectAfterUnmounted = true;
      $axure.ex.beforeUnmountFn = function () {};
      $axure.beforeUnmount = function (callback, redirect) {
        $axure.ex.redirectAfterUnmounted = redirect || false;
        $axure.ex.beforeUnmountFn = callback;
      };

      // 先缓存原本的跳转方法
      $axure.ex.navigate = $ax.public.navigate;

      // 定义一个新的跳转方法，加入钩子
      $axure.openURL = function(options) {
        if (!options.url) return;
        
        if (options.target === 'frame') {
          // 这里实际的 iframe 在 axure 元件内一层
          options.frame = $(frame).children('iframe').get(0);
        }

        // 执行钩子函数
        $axure.ex.beforeUnmountFn(options);

        // 根据用户传参决定是否继续进行跳转
        if ($axure.ex.redirectAfterUnmounted) {
          $axure.ex.navigate(options);
        }
      };

      // $ax.public.navigate = $ax.navigate = function (options) {
      //   /* do something else */
      //   $axure.ex.navigate(options);
      // };

      $axure.getOverlapData = getOverlapData;
      $axure.getDiffData = getDiffData;

    });

  }

  function Utils() {
    /**
     * 拓展原生JS
     */

    // 增加一个将字符串去除换行符的方法
    Object.defineProperty(String.prototype, 'minify', {
      enumerable: false,
      configurable: true,
      value: function () {
        return this.replace(/ *[\r|\n] */gm, '')
      }
    });


    /**
     * 拓展 JQuery
     */

    // 增加一个判断变量是否符合 JSON 格式的方法
    $.isJSON = function (str) {
      try {
        if (typeof JSON.parse(str) == "object") {
          return true;
        }
      } catch (e) {}
      return false;
    };

    // 增加一个判断 DOM 元素是否存在 Attribute 属性的方法
    $.fn.extend({
      'hasAttr': function (name) {
        let _attr = $(this).attr(name);
        if (typeof _attr !== typeof undefined && _attr !== false) {
          return true;
        } else {
          return false;
        }
      }
    });
  }

  function layout(mode) {
    if (self == top) return;
    mode = mode !== undefined ? mode : 0;

    let _mode = {
      hotspots: false,
      notes: false,
      scale: [0, 1, 2][1],
      console: false,
      sitemap: false,
      minimal: false,
      website: true
    };

    setTimeout(() => {
      let shell = window.parent.document;
      let hash = shell.location.hash;

      if (mode > 0) {
        if (hash.indexOf('&fn=0') < 0) {
          $(shell).find('#showNotesOption').click();
        }
        if (hash.indexOf('&hi=1') > -1) {
          $(shell).find('#showHotspotsOption').click();
        }
      }

      if (mode === 0) {
        $(shell).find('#maximizePanelContainer').show();
        $(shell).find(`.vpScaleOption[val="0"]`).click();
      } else if (mode === 1) {
        $(shell).find(`.vpScaleOption[val="0"]`).click();
        $(shell).find('#interfaceControlFrameMinimizeContainer').click();
        $(shell).find('#maximizePanelContainer').hide();
      } else if (mode === 2) {
        $(shell).find(`.vpScaleOption[val="1"]`).click();
        $(shell).find('#interfaceControlFrameMinimizeContainer').click();
        $(shell).find('#maximizePanelContainer').hide();
      } else if (mode === 3) {
        $(shell).find(`.vpScaleOption[val="2"]`).click();
        $(shell).find('#interfaceControlFrameMinimizeContainer').click();
        $(shell).find('#maximizePanelContainer').hide();
      }
    }, 0);
  }

  function jsonbin(options) {
    let This = this;
    let fn = function(){};
    let host = 'https://api.jsonbin.io/v3';

    let { binId, binName, collectionId, collectionName, collectionSort='desc', apiKey, useVersion=false, isPrivate=false } = options;
    This.binId = binId;

    return {
      create: (callback) => {
        if (This.binId) { callback(This.binId); return; }
        let data = { axure: 'axlib' };
        $.ajax({
          url: `${host}/b`,
          data: JSON.stringify({ data }),
          type: 'POST',
          beforeSend: function (req) {
            req.setRequestHeader('Content-Type', 'application/json');
            req.setRequestHeader('X-Bin-Private', isPrivate);
            req.setRequestHeader('X-Master-Key', apiKey);
            if (binName) { req.setRequestHeader('X-Bin-Name', binName); }
            if (collectionId) { req.setRequestHeader('X-Collection-Id', collectionId); }
          },
          contentType: 'application/json; charset=utf-8',
          dataType: 'json',
          success: callback || fn,
          error: err => { console.log(err); }
        });
      },
      read: (arg1, arg2) => {
        let callback, path;
        if (arg1 !== undefined && arg2 === undefined) {
          callback = arg1;
        }
        else if (arg1 !== undefined && arg2 !== undefined) {
          path = arg1;
          callback = arg2;
        }
        else if (arg1 === undefined && arg2 === undefined) {
          callback = fn;
        }

        $.ajax({
          url: `${host}/b/${This.binId}/latest`,
          type: 'GET',
          beforeSend: function (req) {
            if (path) { req.setRequestHeader('X-Dot-Path', path); }
            if (apiKey) { req.setRequestHeader('X-Master-Key', apiKey); }
          },
          contentType: 'application/json; charset=utf-8',
          dataType: 'json',
          success: callback,
          error: err => { console.log(err); }
        });
      },
      update: (data, callback) => {
        $.ajax({
          url: `${host}/b/${This.binId}`,
          data: JSON.stringify({ data }),
          type: 'PUT',
          beforeSend: function (req) {
            req.setRequestHeader('X-Bin-Meta', true);
            req.setRequestHeader('X-Bin-Versioning', useVersion);
            if (apiKey) { req.setRequestHeader('X-Master-Key', apiKey); }
          },
          contentType: 'application/json; charset=utf-8',
          dataType: 'json',
          success: callback || fn,
          error: err => { console.log(err); }
       });
      },
      del: (callback) => {
        $.ajax({
          url: `${host}/b/${This.binId}`,
          type: 'DELETE',
          beforeSend: function (req) {
            if (apiKey) { req.setRequestHeader('X-Master-Key', apiKey); }
          },
          contentType: 'application/json; charset=utf-8',
          dataType: 'json',
          success: callback || fn,
          error: err => { console.log(err); }
        });
      },
      collection: (callback) => {
        $.ajax({
          url: `${host}/c`,
          type: 'POST',
          beforeSend: function (req) {
            if (apiKey) { req.setRequestHeader('X-Master-Key', apiKey); }
            if (collectionName) { req.setRequestHeader('X-Collection-Name', collectionName); }
          },
          success: callback,
          error: err => { console.log(err); }
        });
      },
      list: (callback) => {
        let sort = collectionSort || 'desc';
        $.ajax({
          url: `${host}/c/${collectionId}/bins`,
          type: 'GET',
          beforeSend: function (req) {
            req.setRequestHeader('X-Sort-Order', sort);
            if (apiKey) { req.setRequestHeader('X-Master-Key', apiKey); }
          },
          dataType: 'json',
          success: callback || fn,
          error: err => { console.log(err); }
        });
      },
      help: () => {
        let msg = [
          '# 初始化',
          'let bin = new axlib.jsonbin({ binId, binName, collectionId, collectionName, apiKey, userVersion, isPrivate });\n',
          '# 创建 Collection',
          'bin.collection(callback); // apiKey & collectionName is required.\n',
          '# 枚举 Collection 内的 bin',
          'bin.list(callback); // apiKey is required, collectionSoft is optional.\n',
          '# 创建 bin',
          'bin.create(callback); // apiKey, isPrivate is required, binName, collectionId is optional.\n',
          '# 读取 bin',
          'bin.read(path, callback); // path, callback, apiKey is optional.\n',
          '# 更新 bin',
          'bin.update(data, callback); // data is required, useVersion, apiKey is optional.\n',
          '# 删除 bin',
          'bin.del(callback); // apiKey is required.\n',
        ].join('\n');
        trace(msg);
      }
    };
  }

  function loading(container, show, options) {
    options = options || { color:'#fff', opacity:0.2, scale:1.2, delay:0, dur:1, blending:'difference' };
    if (show === undefined) { show = true; }
    let display = show ? 'block' : 'none';

    let svg = `
      <svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 38 38" stroke="${options.color}" style="transform:scale(${options.scale});">
        <g fill="none" fill-rule="evenodd">
          <g transform="translate(1 1)" stroke-width="2">
            <circle stroke-opacity="${options.opacity}" cx="18" cy="18" r="18"/>
            <path d="M36 18c0-9.94-8.06-18-18-18">
              <animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="${options.dur}s" repeatCount="indefinite"/>
            </path>
          </g>
        </g>
      </svg>
    `;
    svg = svg.minify();

    if (!window.$axure.loadingCSSRulesInited) {
      $('head').append(`
        <style>
          html, body { height: 100%; }
          .ax-loading { display:${display}; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: transparent; z-index: 99999; mix-blend-mode:${options.blending}; pointer-events:none; transform: scale(.5); }
          .ax-loading-svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
        </style>
      `);
      window.$axure.loadingCSSRulesInited = true;
    }

    if ($(container).children('.ax-loading').length === 0) {
      $(container).append(`<div class="ax-loading"><div class="ax-loading-svg">${svg}</div></div>`);
    }
    return $(container).children('.ax-loading').get(0);
  }

  function trace() {
    console.group('%cAXLIB', 'color:#fff; font-weight:bold; background:#08f; padding:4px 8px; border-radius:3px;');
    console.log(...arguments);
    console.groupEnd();
  }

  function hosting(cloud) {
    cloud = cloud || 'axhub';
    if (cloud == 'axhub') {
      let btnUpload = $('#axhub-rp9-root a i', top.document);
      if (btnUpload.length === 0) {
        trace('无法同步，你需要先为浏览器安装axhub插件！');
      } else {
        btnUpload.click();
      }
    }
  }

  function find () {
    let args = [...arguments];
    let obj = {};
    for (let i = 0; i < args.length; i++) {
      let name = args[i];
      let el = $axure('@' + name);
      if (el.$().length > 0) {
        obj[name] = el;
      }
    }
    return obj;
  }

  function use(args, cb) {
    args = typeof args === 'string' ? [args] : args;
    if (!args.forEach) { console.log('Use array or string as argument!'); return; }
    args.forEach(mod => {
      let plugin = _plugins[mod];
      if (!plugin) { console.log('Plugin not found!'); return; }
      yepnope({
        test: getNestedValue(window, plugin['vars']),
        nope: plugin.urls,
        complete: () => {
          let res = getNestedValue(window, plugin['vars']);
          if (!axlib.plugins) { axlib.plugins = {}; }
          axlib.plugins[mod] = res;
          cb(axlib.plugins);
        }
      });
    });
  }

  function useYepNope(callback) {
    if (!window.yepnope) {
      let ynURL = $axure.getGlobalVariable('YepNope') || 'https://ax.minicg.com/yepnope.min.js';
      $.getScript(ynURL, callback);
    } else {
      callback();
    }
  }

  function tree(_data) {
    let scanArr = [];
    let scanObj = [];
    let maxLevel = _data.length > 0 ? Object.keys(_data[0]).length : 0;
  
    const unique = function(arr, obj) {
      let _arr = [], _obj = [];
      for(let i=0; i<arr.length; i++) {
        if ( !_arr.includes( arr[i]) ) {
          _arr.push( arr[i] );
          _obj.push( obj[i] );
        }
      }
      return [_arr, _obj];
    };
  
    for (let i=0; i<maxLevel; i++) {
      let cid = `c${i+1}`;
      let arr = [], obj = [];
      for (let k in _data) {
        let item = _data[k];
        arr.push(item[cid]);
        obj.push({value:item[cid], label:item[cid], parent:item[`c${i}`], children:[]});
      }
      scanArr[i] = unique(arr, obj)[0];
      scanObj[i] = unique(arr, obj)[1];
    }
  
    let len = scanObj.length;
    for (let i=0; i<len; i++) {
      let curIdx = len-i-1;
      let nextIdx = len-i-2;
      let curItem = scanObj[curIdx];
      let nextItem = scanObj[nextIdx];
  
      for (let k in curItem) {
        let _current = curItem[k];
        for (let n in nextItem) {
          let _next = nextItem[n];
          if (_current.parent == _next.value) {
            _next.children.push(_current);
          }
        }
      }
    }
  
    for (let t=0; t<scanObj[0].length; t++) {
      delete scanObj[0][t].parent;
    }
    
    return scanObj[0];
  }

  function trimTreeData(arr) {
    for (let i=0; i<arr.length; i++) {
      let item = arr[i];
      if (item.children.length > 0) {
        if (item.children.length == 1 && !item.children[0].value) {
          delete item.children;
        } else {
          trimTreeData(item.children);
        }
      } else if (item.children && item.children.length == 0) {
        delete item.children;
      }
    }
  }

  function saveTextFile(str, fileName) {
    str = str || 'axlib';
    fileName = fileName || 'text.txt';
    const blob = new Blob([str], { type: 'application/json;charset=utf-8' });
    const objectURL = URL.createObjectURL(blob);

    const anchor = document.createElement('a');
    anchor.href = objectURL;
    anchor.download = fileName;
    anchor.click();

    URL.revokeObjectURL(objectURL);
  }

  function testExp(params) {
    params = params || {};
    let count = 0;
    let fn = function(){};
    let { exp='', success=fn, fail=fn, interval=200, timeout=5000, debug=false } = params;
    if (timeout === -1 || timeout === false) { timeout = 1e16; }
    let timer = setInterval(()=>{
        if (getNestedValue(window, exp)) {
            clearInterval(timer);
            success();
        } else {
            if (debug) { console.log(count); }
            count += interval;
            if (count >= timeout) {
                clearInterval(timer);
                fail();
            }
        }
    }, interval);
  }

  function getNestedValue(obj, key) {
    return key.split(".").reduce(function(result, key) {
       return result[key] 
    }, obj);
  }

  function getOverlapData(data1, data2) {
    // let arr = [JSON.stringify(data1), JSON.stringify(data2)];
    // arr.sort((a, b) => { return a.length < b.length; });
    // let str1 = arr[0], str2 = arr[1];
    // str1 = str1.substr(1,str1.length-2);
    // str2 = str2.substr(1,str2.length-2);
    // let str = str1.replace(str2, '');
    // str = str.replace(/\},,\{/g, '},{');
    // str = `[${str}]`;
    // return JSON.parse(str);

    let bigData = data1.length > data2.length ? data1 : data2;
    let smallData = data1.length < data2.length ? data1 : data2;
    smallData.forEach((a, i) => {
      let _a = JSON.stringify(a);
      bigData.forEach((b, j) => {
        let _b = JSON.stringify(b);
        if (_a == _b) { bigData[j] = null; }
      });
      bigData = bigData.filter(Boolean);
    });
    return bigData;
  }

  function getDiffData(data1, data2) {
    // 弃用，会误删相同的数据
    let diff = data1.concat(data2).map(item => JSON.stringify(item)).filter((value, idx, arr)=>{
      return arr.indexOf(value) == arr.lastIndexOf(value);
    });
    return JSON.parse(`[${diff}]`);
  }

  function loadJsModule(url, callback) {
    let script = document.createElement('script');
    script.onload = callback || function(){};
    script.setAttribute('type', 'module');
    script.setAttribute('src', url);
    document.querySelector('body').appendChild(script);
  }

}());